/**
Engine.cpp
Title: Binge Engine Program File
Descr: Engine Class

Copyright (c) 2010, Brant Ryan Anderson
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <ORGANIZATION> nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/

#include "../../binge/core/Engine.h"

namespace binge
{

Engine::Engine(void)
{
    srand((unsigned int)time(NULL));

    maximizeProcessor = false;
    frameCountCore = 0;
    frameRateCore = 0;
    frameCountReal = 0;
    frameRateReal = 0;
    engineFrameRateMS = 16;     //update with 60 FPS timing(16ms)
                                //update with 30 FPS timing(32ms)
    mouseVisible = true;
    gameState = GAMESTATE_NONE;

    versionMajor = VERSION_MAJOR;
    versionMinor = VERSION_MINOR;
    versionRevision = VERSION_REVISION;
    versionRelease = VERSION_RELEASE;

    //set default values
    SetGameTitle("Binge Engine");
    SetScreenWidth(800);
    SetScreenHeight(600);
    SetColorDepth(32);
    SetFullscreen(false);

    prevTexture = 0;

    quadtree = 0;

    // audio = new Audio();
}

Engine::~Engine(void)
{
    /*
    delete audio;
    audio = 0;
    */
    delete quadtree;
    quadtree = 0;
}

std::string
Engine::GetVersionText(void)
{
    std::ostringstream s;
    s << "Binge " << versionMajor << "." << versionMinor << "." << versionRevision << "." << versionRelease;
    return s.str();
}

void
Engine::Message(std::string message, std::string title)
{
    std::cout << title.c_str() << ": " << message.c_str();
}

void
Engine::FatalError(std::string message, std::string title)
{
    this->Message(message, title);
    Shutdown();
}

bool
Engine::InitSFML(void)
{
    sf::VideoMode vidMode = sf::VideoMode(this->GetScreenWidth(), this->GetScreenHeight(), this->GetColorDepth());
    if (!vidMode.IsValid())
    {
        return false;
    }
    unsigned long winStyle;
    if (this->GetFullscreen())
    {
        winStyle = sf::Style::Fullscreen;
    } else {
        winStyle = sf::Style::Close;
    }
    this->screen.Create(vidMode, this->GetGameTitle(), winStyle);
    this->screen.ShowMouseCursor(mouseVisible);

    return true;
}

bool
Engine::Init(void)
{
    // Initialize SFML
    if (InitSFML() == false)
    {
        this->FatalError("Error initializing SFML!", "initSFML()");
        return false;
    }
    // Initialize Audio
    if (audio.Init() == false)
    {
        this->Message("Error initializing Audio.", "Audio Disabled");
        audio.disabled = true;
    }

    // Initialize the Quadtree
    if (quadtree != 0)
    {
        delete quadtree;
        quadtree = 0;
    }
    if (map.GetWidth() > GetScreenWidth() && map.GetHeight() > GetScreenHeight())
    {
        quadtree = new Quadtree(2, map.GetWidth(), map.GetHeight());
    }
    else
    {
        quadtree = new Quadtree(2, GetScreenWidth(), GetScreenHeight());
    }

    return true;
}

bool
Engine::RenderStart()
{
    screen.Clear();

    return true;
}

bool
Engine::RenderStop()
{
    screen.Display();

    return true;
}

void
Engine::Shutdown()
{
    gameover = true;
}

void
Engine::Update(	void (BaseGame::*game_update)(void),
				void (BaseGame::*game_render)(void),
				BaseGame* basegame)
{
    //calculate core framerate
    frameCountCore++;
    if (coreTimer.Stopwatch(999))
    {
        frameRateCore = frameCountCore;
        frameCountCore = 0;
    }

    //fast update with no timing
    (basegame->*game_update)();

    //update entities with no timing
    if (GetGameMode() != GAMESTATE_PAUSED)
    {
        //AnimationSystem();
        animation.Animate();
        MovementSystem(&BaseGame::GOBMoved, basegame);
    }

    //perform global collision testing at 20 FPS (50ms) 30 FPS (32ms)
    if (GetGameMode() != GAMESTATE_PAUSED && collisionTimer.Stopwatch(32))
    {
        CollisionSystem(&BaseGame::GOBCollision, basegame);
    }

    //update with 60 FPS timing(16 = 62.5 max)
    //update with 30 FPS timing because of performance issues(32 = 31.25 max)
    if (!timedUpdate.Stopwatch(engineFrameRateMS))
    {
        if (!this->GetMaximizeProcessor())
        {
            usleep(1);
        }
    }
    else
    {
        //calculate real framerate
        frameCountReal++;
        if (realTimer.Stopwatch(999))
        {
            frameRateReal = frameCountReal;
            frameCountReal = 0;
        }

        // If a music play list is being used, cycle through music play list
        if (audio.playlist)
        {
            if (!audio.Music_IsPlaying() && !audio.Music_IsPaused()) audio.Music_ListNext();
        }

        //begin rendering
        this->RenderStart();

        //render functions provided for entities
        if (GetGameMode() != GAMESTATE_PAUSED)
        {
            RenderSystem(&BaseGame::GOBRendered, basegame);
        }
        //render basegame
        (basegame->*game_render)();

        //done rendering
        this->RenderStop();
    }
    GOBCleanup(&BaseGame::GOBRemoved, basegame);
}

/*
// Engine Subsystems
void
Engine::AnimationSystem(void)
{
    for (int j = 0; j < animation.GetCount(); j++)
    {
        int set = 0;
        int objId = animation.GetIdByIndex(j);
        unsigned int size = animation.GetAnimSetList(objId).size();
        unsigned int frameTimer = 0;
        for (unsigned int x = 0; x < size; x++)
        {
            if (animation.GetAnimSetList(objId)[x].GetName() == animation.GetName(animation.GetIdByIndex(j)))
            {
                set = x;
                x = size;
            }
        }
        animation.SetCurrentFrame(objId, animation.GetAnimSetList(objId)[set].GetCurrentFrame());
        animation.SetFrameTimer(objId, animation.GetAnimSetList(objId)[set].GetFrameTimer());

        // update frame based on time
        frameTimer = animation.GetFrameTimer(objId);
        if (frameTimer > 0)
        {
            if (animation.timer.GetTimer() > animation.GetFrameStart(objId) + frameTimer)
            {
                //reset animation timer
                animation.SetFrameStart(objId, animation.timer.GetTimer());
                animation.NextFrame(objId, set);
            }
        }
        else
        {
            //no animation timer--update at cpu clock speed
            animation.NextFrame(objId, set);
        }
    }
}
*/

void
Engine::MovementSystem(	void (BaseGame::*game_gob_moved)(int id),
                        BaseGame* basegame)
{
    for (int x = 0; x < movement.GetCount(); x++)
    {
        int objId = movement.GetIdByIndex(x);

        float xvel = 0.f;
        float yvel = 0.f;

        if (movement.GetMoveTimer(objId) > 0)
        {
            if (movement.timer.GetTimer() > movement.GetMoveStart(objId) + movement.GetMoveTimer(objId))
            {
                movement.SetMoveStart(objId, movement.timer.GetTimer());
                if (position.Has(objId))
                {
                    // Jumping Stuff
                    if (gravity.Has(objId) && collision.Has(objId))
                    {
                        int status = gravity.GetStatus(objId);
                        if (status == GRAVITY_JUMPING)
                        {
                            float jumpLeft = gravity.GetGround(objId)-gravity.GetJumpPeak(objId);

                            if (collision.GetBottom(objId) >= jumpLeft)
                            {
                                // Jumping
                                jumpLeft = jumpLeft - collision.GetBottom(objId);
                                yvel = (jumpLeft * gravity.GetJumpSpeed(objId)) - 1.0f;

                                movement.SetYVelocity(objId, yvel);
                            }
                            else
                            {
                                // Falling
                                gravity.SetStatus(objId, binge::GRAVITY_FALLING);
                            }
                        }
                        else if (status == GRAVITY_FALLING)
                        {
                            movement.SetYVelocity(objId, gravity.GetGravity(objId));
                        }
                        else if (status == GRAVITY_WALKING)
                        {
                            // Stub
                        }
                    }
                    yvel = movement.GetYVelocity(objId);
                    xvel = movement.GetXVelocity(objId);

                    // Tile Collision:
                    if (collision.Has(objId))
                    {
                        TileCollisions(objId, xvel, yvel, &BaseGame::TileCollision, basegame);
                    }
                    else // No Tile Collision
                    {
                        xvel = movement.GetXVelocity(objId);
                        yvel = movement.GetYVelocity(objId);

                        position.SetX(objId, position.GetX(objId) + xvel);
                        position.SetY(objId, position.GetY(objId) + yvel);
                    }
                    (basegame->*game_gob_moved)(objId);
                }
            }
        } else {
            if (position.Has(objId))
            {
                // Jumping Stuff
                if (gravity.Has(objId) && collision.Has(objId))
                {
                    int status = gravity.GetStatus(objId);
                    if (status == GRAVITY_JUMPING)
                    {
                        float jumpLeft = gravity.GetGround(objId)-gravity.GetJumpPeak(objId);

                        if (collision.GetBottom(objId) >= jumpLeft)
                        {
                            // Jumping
                            jumpLeft = jumpLeft - collision.GetBottom(objId);
                            yvel = (jumpLeft * gravity.GetJumpSpeed(objId)) - 1.0f;

                            movement.SetYVelocity(objId, yvel);
                        }
                        else
                        {
                            // Falling
                            gravity.SetStatus(objId, binge::GRAVITY_FALLING);
                        }
                    }
                    else if (status == GRAVITY_FALLING)
                    {
                        movement.SetYVelocity(objId, gravity.GetGravity(objId));
                    }
                    else if (status == GRAVITY_WALKING)
                    {
                        // Stub
                    }
                }
                yvel = movement.GetYVelocity(objId);
                xvel = movement.GetXVelocity(objId);

                // Tile Collision:
                if (collision.Has(objId))
                {
                    TileCollisions(objId, xvel, yvel, &BaseGame::TileCollision, basegame);
                }
                else // No Tile Collision Collision
                {
                    xvel = movement.GetXVelocity(objId);
                    yvel = movement.GetYVelocity(objId);

                    position.SetX(objId, position.GetX(objId) + xvel);
                    position.SetY(objId, position.GetY(objId) + yvel);
                }
                (basegame->*game_gob_moved)(objId);
            }
        }
    }
}

void
Engine::RenderSystem(	void (BaseGame::*game_gob_rendered)(int id),
                        BaseGame* basegame)
{

    // Camera code:
    float xoffset = 0;
    float yoffset = 0;

    if (map.GetCameraObject() != -1)
    {
        float half_w = GetScreenWidth()/2;
        float half_h = GetScreenHeight()/2;
        float xpos = 0;
        float ypos = 0;
        if (animation.Has(map.GetCameraObject()) && position.Has(map.GetCameraObject()))
        {
            xpos = position.GetX(map.GetCameraObject()) + animation.GetSheet(map.GetCameraObject()).GetFrameWidth()/2;
            ypos = position.GetY(map.GetCameraObject()) + animation.GetSheet(map.GetCameraObject()).GetFrameHeight()/2;
        }
        else if (position.Has(map.GetCameraObject()))
        {
            xpos = position.GetX(map.GetCameraObject()) + render.GetSprite(map.GetCameraObject()).GetImage()->GetWidth()/2;
            ypos = position.GetY(map.GetCameraObject()) + render.GetSprite(map.GetCameraObject()).GetImage()->GetHeight()/2;
        }
        xoffset = xpos - half_w;
        yoffset = ypos - half_h;
        if (xoffset < 0)
        {
            xoffset = 0;
        }
        if (xoffset > map.GetWidth()-GetScreenWidth())
        {
            xoffset = map.GetWidth()-GetScreenWidth();
        }
        if (map.GetWidth() < GetScreenWidth())
        {
            xoffset = (GetScreenWidth()/2 - map.GetWidth()/2)*-1;
        }
        if (yoffset < 0)
        {
            yoffset = 0;
        }
        if (yoffset > map.GetHeight()-GetScreenHeight())
        {
            yoffset = map.GetHeight()-GetScreenHeight();
        }
        if (map.GetHeight() < GetScreenHeight())
        {
            yoffset = (GetScreenHeight()/2 - map.GetHeight()/2)*-1;
        }
    }

    // Render code:
    for (int x = 0; x < render.GetCount(); x++)
    {
        int objId = render.GetIdByIndex(x);
        sf::Rect<float> screenRect;

        screenRect.Left = 0;
        screenRect.Top = 0;
        screenRect.Right = GetScreenWidth();
        screenRect.Bottom = GetScreenHeight();

        if (animation.Has(objId) && position.Has(objId))
        {
            int frameWidth = animation.GetSheet(objId).GetFrameWidth();
            int frameHeight = animation.GetSheet(objId).GetFrameHeight();
            sf::Rect<float> boxRect;

            boxRect.Left = position.GetX(objId)-xoffset;
            boxRect.Top = position.GetY(objId)-yoffset;
            boxRect.Right = frameWidth+boxRect.Left;
            boxRect.Bottom = frameHeight+boxRect.Top;

            if (boxRect.Intersects(screenRect))
            {
                animation.GetSheet(objId).GetSprite().SetPosition(boxRect.Left, boxRect.Top);
                screen.Draw(animation.GetSheet(objId).GetSprite());

                (basegame->*game_gob_rendered)(objId);
            }
        }
        else if (position.Has(objId))
        {
            int imgWidth = render.GetSprite(objId).GetImage()->GetWidth();
            int imgHeight = render.GetSprite(objId).GetImage()->GetHeight();
            sf::Rect<float> boxRect;

            boxRect.Left = position.GetX(objId)-xoffset;
            boxRect.Top = position.GetY(objId)-yoffset;
            boxRect.Right = imgWidth+boxRect.Left;
            boxRect.Bottom = imgHeight+boxRect.Top;

            if (boxRect.Intersects(screenRect))
            {
                render.GetSprite(objId).SetPosition(boxRect.Left, boxRect.Top);
                screen.Draw(render.GetSprite(objId));

                (basegame->*game_gob_rendered)(objId);
            }
        }
    }
}

void
Engine::CollisionSystem(    void (BaseGame::*game_gob_collision)(int id1, int id2, int sides),
                            BaseGame* basegame)
{

    // Use a Quadtree to speed up collision detection.
    quadtree->Update(collision);
    for(unsigned int i = 0; i < quadtree->collisionNodes.size(); i++)
    {
        for(unsigned int j = 0; j < quadtree->collisionNodes[i]->objects.size(); j++)
        {
            int objId1 = quadtree->collisionNodes[i]->objects[j];
            if(gameObjects.IsAlive(objId1) && collision.IsTile(objId1) == false)
            {
                for(unsigned int k = 0; k < quadtree->collisionNodes[i]->objects.size(); k++)
                {
                    int objId2 = quadtree->collisionNodes[i]->objects[k];
                    if(objId1 != objId2 && gameObjects.IsAlive(objId2) && collision.IsTile(objId2) == false)
                    {
                        if(Collision(objId1, objId2))
                        {
                            // Notify game of collision
                            int sides = CheckCollisionSide(collision.GetRectangle(objId2), collision.GetRectangle(objId1));
                            //if (gameObjects.IsTile(objId1) == false && gameObjects.IsTile(objId2) == true)
                            //{
                            //    TileCollisons(sides, objId1, objId2);
                            //}
                            (basegame->*game_gob_collision)(objId1, objId2, sides);
                        }
                    }
                }
            }
        }
    }

/*
    // Check every object against each other.
    for (int i = 0; i < collision.GetCount(); i++)
    {
        //point local entity to entity contained in the list
        int objId1 = collision.GetIdByIndex(i);

        if (gameObjects.IsAlive(objId1))
        {
            //test all other entities for collision
            for (int j = 0 ; j < collision.GetCount(); j++)
            {
                //point local entity to entity contained in the list
                int objId2 = collision.GetIdByIndex(j);

                //if other entity is active and not same as first entity...
                if (gameObjects.IsAlive(objId2) && objId1 != objId2)
                {
                    //test for collision
                    if (Collision(objId1, objId2))
                    {
                        //notify game of collision
                        (basegame->*game_gob_collision)(objId1, objId2);
                    }
                }
            }
        }
    }
*/
}

bool
Engine::Collision(int objId1, int objId2)
{
    switch (collision.GetType(objId1))
    {
    case COLLISION_RECT:
        return CollisionBR(objId1, objId2);
        break;
    case COLLISION_DIST:
        return CollisionD(objId1, objId2);
        break;
    case COLLISION_NONE:
    default:
        return false;
    }
}

bool
Engine::CollisionBR(int objId1, int objId2)
{
    bool ret = false;
    sf::Rect<float> ra;
    sf::Rect<float> rb;
    ra = collision.GetRectangle(objId1);
    rb = collision.GetRectangle(objId2);

    //are any of entity b's corners intersecting entity a?
    if (ra.Intersects(rb)) ret = true;

    return ret;
}

bool
Engine::CollisionD(int objId1, int objId2)
{
    double radius1, radius2;
    sf::Rect<float> ra = collision.GetRectangle(objId1);
    sf::Rect<float> rb = collision.GetRectangle(objId2);

    //calculate radius 1
    if (ra.Right > ra.Bottom)
        radius1 = ra.Right / 2;
    else
        radius1 = ra.Bottom / 2;

    //point = center of entity 1
    float x1 = ra.Left + radius1;
    float y1 = ra.Top + radius1;
    Vector3 vector1(x1, y1, 0.0);

    //calculate radius 2
    if (rb.Right > rb.Bottom)
        radius2 = rb.Right / 2;
    else
        radius2 = rb.Bottom / 2;

    //point = center of entity 2
    float x2 = rb.Left + radius2;
    float y2 = rb.Top + radius2;
    Vector3 vector2(x2, y2, 0.0);

    //calculate distance
    float dist = vector1.Distance( vector2 );

    //return distance comparison
    return (dist < radius1 + radius2);
}

int
Engine::CheckCollisionSide(sf::Rect<float> r1, sf::Rect<float> r2)
{
    int FoundCollisions = COLLISION_NO;
    // 0* - no collision
    // 1* - collision from top
    // 2* - collision from bottom
    // 4* - collision from left
    // 5  - collision from left/top
    // 6  - collision from left/bottom
    // 8* - collision from right
    // 9  - collision from right/top
    // 10 - collision from right/bottom

    if (r1.Top > r2.Top)
        // r2 is above r1
        FoundCollisions = COLLISION_TOP;

    if (r1.Top < r2.Top)
        // r2 is below r1
        FoundCollisions = COLLISION_BOTTOM;

    if (r1.Left > r2.Left)
        // r2 stands left of r1
        FoundCollisions = FoundCollisions | COLLISION_LEFT;

    if (r1.Left < r2.Left)
        // r2 stands right of r1
        FoundCollisions = FoundCollisions | COLLISION_RIGHT;

    return FoundCollisions;
}

void
Engine::GetMyCorners(int objId, float xvel, float yvel, bool axis_is_x)
{
    float downY;
    float centerY;
    float upY;
    float leftX;
    float centerX;
    float rightX;

    float colWidth = collision.GetRight(objId) - collision.GetLeft(objId);
    float colHeight = collision.GetBottom(objId) - collision.GetTop(objId);

    if (axis_is_x)
    {
        downY = collision.GetBottom(objId) - 1;
        centerY = collision.GetTop(objId) + colHeight / 2;
        upY = collision.GetTop(objId) + 1;
        leftX = collision.GetLeft(objId) + xvel + 1;
        centerX = collision.GetLeft(objId) + colWidth / 2 + xvel;
        rightX = collision.GetRight(objId) + xvel - 1;

        //std::cout << "  downY: " << downY << std::endl;
        //std::cout << "centerY: " << centerY << std::endl;
        //std::cout << "    upY: " << upY << std::endl;
        //std::cout << "  leftX: " << leftX << std::endl;
        //std::cout << "centerX: " << centerX << std::endl;
        //std::cout << " rightX: " << rightX << std::endl;
        //std::cout << std::endl;
    }
    else
    {
        downY = collision.GetBottom(objId) + yvel - 1;
        centerY = collision.GetTop(objId) + colHeight/2 + yvel;
        upY = collision.GetTop(objId) + yvel + 1;
        leftX = collision.GetLeft(objId)+1;
        centerX = collision.GetLeft(objId) + colWidth / 2;
        rightX = collision.GetRight(objId) - 1;
    }

    // Set to true if area is walkable.
    tcCorners.upLeft = true;
    tcCorners.centerLeft = true;
    tcCorners.downLeft = true;
    tcCorners.upRight = true;
    tcCorners.centerRight = true;
    tcCorners.downRight = true;
    tcCorners.upCenter = true;
    tcCorners.downCenter = true;
    tcCorners.upLeftIds = CollidesWithPoint(leftX, upY);
    tcCorners.centerLeftIds = CollidesWithPoint(leftX, centerY);
    tcCorners.downLeftIds = CollidesWithPoint(leftX, downY);
    tcCorners.upRightIds = CollidesWithPoint(rightX, upY);
    tcCorners.centerRightIds = CollidesWithPoint(rightX, centerY);
    tcCorners.downRightIds = CollidesWithPoint(rightX, downY);
    tcCorners.upCenterIds = CollidesWithPoint(centerX, upY);
    tcCorners.downCenterIds = CollidesWithPoint(centerX, downY);

    for (unsigned int j = 0; j < tcCorners.upLeftIds.size(); j++)
    {
        if (collision.IsTile(tcCorners.upLeftIds[j]))
        {
            tcCorners.upLeft = false;
        }
    }
    for (unsigned int j = 0; j < tcCorners.centerLeftIds.size(); j++)
    {
        if (collision.IsTile(tcCorners.centerLeftIds[j]))
        {
            tcCorners.centerLeft = false;
        }
    }
    for (unsigned int j = 0; j < tcCorners.downLeftIds.size(); j++)
    {
        if (collision.IsTile(tcCorners.downLeftIds[j]))
        {
            tcCorners.downLeft = false;
        }
    }
    for (unsigned int j = 0; j < tcCorners.upRightIds.size(); j++)
    {
        if (collision.IsTile(tcCorners.upRightIds[j]))
        {
            tcCorners.upRight = false;
        }
    }
    for (unsigned int j = 0; j < tcCorners.centerRightIds.size(); j++)
    {
        if (collision.IsTile(tcCorners.centerRightIds[j]))
        {
            tcCorners.centerRight = false;
        }
    }
    for (unsigned int j = 0; j < tcCorners.downRightIds.size(); j++)
    {
        if (collision.IsTile(tcCorners.downRightIds[j]))
        {
            tcCorners.downRight = false;
        }
    }
    for (unsigned int j = 0; j < tcCorners.upCenterIds.size(); j++)
    {
        if (collision.IsTile(tcCorners.upCenterIds[j]))
        {
            tcCorners.upCenter = false;
        }
    }
    for (unsigned int j = 0; j < tcCorners.downCenterIds.size(); j++)
    {
        if (collision.IsTile(tcCorners.downCenterIds[j]))
        {
            tcCorners.downCenter = false;
        }
    }
}

void
Engine::TileCollisions(  int objId, float xvel, float yvel,
                        void (BaseGame::*game_tile_collision)(int objId, int tileId, int side),
                        BaseGame* basegame)
{
    // Tile Collision:
    if (position.Has(objId) && collision.Has(objId))
    {
        float colWidth = collision.GetRight(objId) - collision.GetLeft(objId);
        float colHeight = collision.GetBottom(objId) - collision.GetTop(objId);

        // Y-Axis Movement
        GetMyCorners(objId, xvel, yvel, false);
        // Hit from below - works!
        if (yvel < 0)
        {
            if(tcCorners.upLeft && tcCorners.upRight && tcCorners.upCenter)
            {
                position.SetY(objId, position.GetY(objId) + yvel);
                collision.SetTop(objId, collision.GetTop(objId) + yvel);
                collision.SetBottom(objId, collision.GetTop(objId) + colHeight);
            }
            else
            {
                if (gravity.Has(objId))
                {
                    gravity.SetStatus(objId, GRAVITY_FALLING);
                }

                int upLeftId = -1;
                int upCenterId = -1;
                int upRightId = -1;
                int tileId1;
                int tileId2;

                float upLeftBottom = 0.f;
                float upCenterBottom = 0.f;
                float upRightBottom = 0.f;
                if (tcCorners.upLeft == false)
                {
                    for (unsigned int k = 0; k < tcCorners.upLeftIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.upLeftIds[k]))
                        {
                            upLeftId = tcCorners.upLeftIds[k];
                            upLeftBottom = collision.GetBottom(tcCorners.upLeftIds[k]);
                        }
                    }
                }
                if (tcCorners.upRight == false)
                {
                    for (unsigned int k = 0; k < tcCorners.upRightIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.upRightIds[k]))
                        {
                            upRightId = tcCorners.upRightIds[k];
                            upRightBottom = collision.GetBottom(tcCorners.upRightIds[k]);
                        }
                    }
                }
                if (tcCorners.upCenter == false)
                {
                    for (unsigned int k = 0; k < tcCorners.upCenterIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.upCenterIds[k]))
                        {
                            upCenterId = tcCorners.upCenterIds[k];
                            upCenterBottom = collision.GetBottom(tcCorners.upCenterIds[k]);
                        }
                    }
                }
                if (!tcCorners.upLeft && !tcCorners.upRight)
                {
                    tileId1 = upLeftId;
                    tileId2 = upRightId;

                    if (upLeftBottom > upRightBottom)
                    {
                        float oldTop = collision.GetTop(objId);
                        float oldY = position.GetY(objId);
                        float yOffset = oldTop - oldY;
                        float newTop = upLeftBottom;
                        float newY = newTop - yOffset;
                        position.SetY(objId, newY);
                        collision.SetTop(objId, newTop);
                        collision.SetBottom(objId, newTop+colHeight);
                    } else {
                        float oldTop = collision.GetTop(objId);
                        float oldY = position.GetY(objId);
                        float yOffset = oldTop - oldY;
                        float newTop = upRightBottom;
                        float newY = newTop - yOffset;
                        position.SetY(objId, newY);
                        collision.SetTop(objId, newTop);
                        collision.SetBottom(objId, newTop+colHeight);
                    }
                    if (tileId1 == tileId2)
                    {
                        (basegame->*game_tile_collision)(objId, tileId1, COLLISION_BOTTOM);
                    }
                    else
                    {
                        (basegame->*game_tile_collision)(objId, tileId1, COLLISION_BOTTOM);
                        (basegame->*game_tile_collision)(objId, tileId2, COLLISION_BOTTOM);
                    }
                }
                else if (!tcCorners.upLeft)
                {
                    float oldTop = collision.GetTop(objId);
                    float oldY = position.GetY(objId);
                    float yOffset = oldTop - oldY;
                    float newTop = upLeftBottom;
                    float newY = newTop - yOffset;
                    position.SetY(objId, newY);
                    collision.SetTop(objId, newTop);
                    collision.SetBottom(objId, newTop+colHeight);

                    tileId1 = upLeftId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_BOTTOM);
                }
                else if (!tcCorners.upRight)
                {
                    float oldTop = collision.GetTop(objId);
                    float oldY = position.GetY(objId);
                    float yOffset = oldTop - oldY;
                    float newTop = upRightBottom;
                    float newY = newTop - yOffset;
                    position.SetY(objId, newY);
                    collision.SetTop(objId, newTop);
                    collision.SetBottom(objId, newTop+colHeight);

                    tileId1 = upRightId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_BOTTOM);
                }
                else if (!tcCorners.upCenter)
                {
                    float oldTop = collision.GetTop(objId);
                    float oldY = position.GetY(objId);
                    float yOffset = oldTop - oldY;
                    float newTop = upCenterBottom;
                    float newY = newTop - yOffset;
                    position.SetY(objId, newY);
                    collision.SetTop(objId, newTop);
                    collision.SetBottom(objId, newTop+colHeight);

                    tileId1 = upCenterId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_BOTTOM);
                }
            }
        }
        // Hit from above - works!
        if (yvel > 0)
        {
            if(tcCorners.downLeft && tcCorners.downRight && tcCorners.downCenter)
            {
                position.SetY(objId, position.GetY(objId) + yvel);
                collision.SetTop(objId, collision.GetTop(objId) + yvel);
                collision.SetBottom(objId, collision.GetTop(objId) + colHeight);
            }
            else
            {
                int downLeftId = -1;
                int downCenterId = -1;
                int downRightId = -1;
                int tileId1;
                int tileId2;

                float downLeftTop = 0.f;
                float downCenterTop = 0.f;
                float downRightTop = 0.f;
                if (tcCorners.downLeft == false)
                {
                    for (unsigned int k = 0; k < tcCorners.downLeftIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.downLeftIds[k]))
                        {
                            downLeftId = tcCorners.downLeftIds[k];
                            downLeftTop = collision.GetTop(tcCorners.downLeftIds[k]);
                        }
                    }
                }
                if (tcCorners.downRight == false)
                {
                    for (unsigned int k = 0; k < tcCorners.downRightIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.downRightIds[k]))
                        {
                            downRightId = tcCorners.downRightIds[k];
                            downRightTop = collision.GetTop(tcCorners.downRightIds[k]);
                        }
                    }
                }
                if (tcCorners.downCenter == false)
                {
                    for (unsigned int k = 0; k < tcCorners.downCenterIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.downCenterIds[k]))
                        {
                            downCenterId = tcCorners.downCenterIds[k];
                            downCenterTop = collision.GetTop(tcCorners.downCenterIds[k]);
                        }
                    }
                }
                if (!tcCorners.downLeft && !tcCorners.downRight)
                {
                    tileId1 = downLeftId;
                    tileId2 = downRightId;
                    if (downLeftTop < downRightTop)
                    {
                        float oldTop = collision.GetTop(objId);
                        float oldY = position.GetY(objId);
                        float yOffset = oldTop - oldY;
                        float newTop = downLeftTop-colHeight;
                        float newY = newTop - yOffset;
                        position.SetY(objId, newY);
                        collision.SetTop(objId, newTop);
                        collision.SetBottom(objId, newTop+colHeight);
                        if (gravity.Has(objId))
                        {
                            gravity.SetStatus(objId, GRAVITY_WALKING);
                            gravity.SetGround(objId, downLeftTop);
                        }
                    } else {
                        float oldTop = collision.GetTop(objId);
                        float oldY = position.GetY(objId);
                        float yOffset = oldTop - oldY;
                        float newTop = downRightTop-colHeight;
                        float newY = newTop - yOffset;
                        position.SetY(objId, newY);
                        collision.SetTop(objId, newTop);
                        collision.SetBottom(objId, newTop+colHeight);
                        if (gravity.Has(objId))
                        {
                            gravity.SetStatus(objId, GRAVITY_WALKING);
                            gravity.SetGround(objId, downRightTop);
                        }
                    }
                    if (tileId1 == tileId2)
                    {
                        (basegame->*game_tile_collision)(objId, tileId1, COLLISION_TOP);
                    }
                    else
                    {
                        (basegame->*game_tile_collision)(objId, tileId1, COLLISION_TOP);
                        (basegame->*game_tile_collision)(objId, tileId2, COLLISION_TOP);
                    }
                }
                else if (!tcCorners.downLeft)
                {
                    float oldTop = collision.GetTop(objId);
                    float oldY = position.GetY(objId);
                    float yOffset = oldTop - oldY;
                    float newTop = downLeftTop-colHeight;
                    float newY = newTop - yOffset;
                    position.SetY(objId, newY);
                    collision.SetTop(objId, newTop);
                    collision.SetBottom(objId, newTop+colHeight);

                    tileId1 = downLeftId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_TOP);

                    if (gravity.Has(objId))
                    {
                        gravity.SetStatus(objId, GRAVITY_WALKING);
                        gravity.SetGround(objId, downLeftTop);

                        if (gravity.HasXEdgeReflect(objId) && movement.GetXVelocity(objId) > 0)
                        {
                            movement.SetXVelocity(objId, movement.GetXVelocity(objId)*-1);
                        }
                    }
                }
                else if (!tcCorners.downRight)
                {
                    float oldTop = collision.GetTop(objId);
                    float oldY = position.GetY(objId);
                    float yOffset = oldTop - oldY;
                    float newTop = downRightTop-colHeight;
                    float newY = newTop - yOffset;
                    position.SetY(objId, newY);
                    collision.SetTop(objId, newTop);
                    collision.SetBottom(objId, newTop+colHeight);

                    tileId1 = downRightId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_TOP);

                    if (gravity.Has(objId))
                    {
                        gravity.SetStatus(objId, GRAVITY_WALKING);
                        gravity.SetGround(objId, downRightTop);

                        if (gravity.HasXEdgeReflect(objId) && movement.GetXVelocity(objId) < 0)
                        {
                            movement.SetXVelocity(objId, movement.GetXVelocity(objId)*-1);
                        }
                    }
                }
                else if (!tcCorners.downCenter)
                {
                    float oldTop = collision.GetTop(objId);
                    float oldY = position.GetY(objId);
                    float yOffset = oldTop - oldY;
                    float newTop = downCenterTop-colHeight;
                    float newY = newTop - yOffset;
                    position.SetY(objId, newY);
                    collision.SetTop(objId, newTop);
                    collision.SetBottom(objId, newTop+colHeight);

                    tileId1 = downCenterId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_TOP);

                    if (gravity.Has(objId))
                    {
                        gravity.SetStatus(objId, GRAVITY_WALKING);
                        gravity.SetGround(objId, downCenterTop);
                    }
                }
            }
        }
        // X-Axis Movement
        GetMyCorners(objId, xvel, yvel, true);
        // Hit from right - works!
        if (xvel < 0)
        {
            if(tcCorners.downLeft && tcCorners.upLeft && tcCorners.centerLeft)
            {
                position.SetX(objId, position.GetX(objId) + xvel);
                collision.SetLeft(objId, collision.GetLeft(objId) + xvel);
                collision.SetRight(objId, collision.GetLeft(objId) + colWidth);
            }
            else
            {
                int downLeftId = -1;
                int centerLeftId = -1;
                int upLeftId = -1;
                int tileId1;
                int tileId2;

                float downLeftRight = 0.f;
                float centerLeftRight = 0.f;
                float upLeftRight = 0.f;
                if (tcCorners.downLeft == false)
                {
                    for (unsigned int k = 0; k < tcCorners.downLeftIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.downLeftIds[k]))
                        {
                            downLeftId = tcCorners.downLeftIds[k];
                            downLeftRight = collision.GetRight(tcCorners.downLeftIds[k]);
                        }
                    }
                }
                if (tcCorners.upLeft == false)
                {
                    for (unsigned int k = 0; k < tcCorners.upLeftIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.upLeftIds[k]))
                        {
                            upLeftId = tcCorners.upLeftIds[k];
                            upLeftRight = collision.GetRight(tcCorners.upLeftIds[k]);
                        }
                    }
                }
                if (tcCorners.centerLeft == false)
                {
                    for (unsigned int k = 0; k < tcCorners.centerLeftIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.centerLeftIds[k]))
                        {
                            centerLeftId = tcCorners.centerLeftIds[k];
                            centerLeftRight = collision.GetRight(tcCorners.centerLeftIds[k]);
                        }
                    }
                }
                if (!tcCorners.downLeft && !tcCorners.upLeft)
                {
                    tileId1 = downLeftId;
                    tileId2 = upLeftId;
                    if (downLeftRight > upLeftRight)
                    {
                        float oldLeft = collision.GetLeft(objId);
                        float oldX = position.GetX(objId);
                        float xOffset = oldLeft - oldX;
                        float newLeft = downLeftRight;
                        float newX = newLeft - xOffset;
                        position.SetX(objId, newX);
                        collision.SetLeft(objId, newLeft);
                        collision.SetRight(objId, newLeft+colWidth);

                    } else {
                        float oldLeft = collision.GetLeft(objId);
                        float oldX = position.GetX(objId);
                        float xOffset = oldLeft - oldX;
                        float newLeft = upLeftRight;
                        float newX = newLeft - xOffset;
                        position.SetX(objId, newX);
                        collision.SetLeft(objId, newLeft);
                        collision.SetRight(objId, newLeft+colWidth);
                    }

                    if (gravity.Has(objId))
                    {
                        if (gravity.HasXWallReflect(objId) && movement.GetXVelocity(objId) < 0)
                        {
                            movement.SetXVelocity(objId, movement.GetXVelocity(objId)*-1);
                        }
                    }

                    if (tileId1 == tileId2)
                    {
                        (basegame->*game_tile_collision)(objId, tileId1, COLLISION_RIGHT);
                    }
                    else
                    {
                        (basegame->*game_tile_collision)(objId, tileId1, COLLISION_RIGHT);
                        (basegame->*game_tile_collision)(objId, tileId2, COLLISION_RIGHT);
                    }
                }
                else if (!tcCorners.downLeft)
                {
                    float oldLeft = collision.GetLeft(objId);
                    float oldX = position.GetX(objId);
                    float xOffset = oldLeft - oldX;
                    float newLeft = downLeftRight;
                    float newX = newLeft - xOffset;
                    position.SetX(objId, newX);
                    collision.SetLeft(objId, newLeft);
                    collision.SetRight(objId, newLeft+colWidth);

                    if (gravity.Has(objId))
                    {
                        if (gravity.HasXWallReflect(objId) && movement.GetXVelocity(objId) < 0)
                        {
                            movement.SetXVelocity(objId, movement.GetXVelocity(objId)*-1);
                        }
                    }

                    tileId1 = downLeftId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_RIGHT);
                }
                else if (!tcCorners.upLeft)
                {
                    float oldLeft = collision.GetLeft(objId);
                    float oldX = position.GetX(objId);
                    float xOffset = oldLeft - oldX;
                    float newLeft = upLeftRight;
                    float newX = newLeft - xOffset;
                    position.SetX(objId, newX);
                    collision.SetLeft(objId, newLeft);
                    collision.SetRight(objId, newLeft+colWidth);

                    if (gravity.Has(objId))
                    {
                        if (gravity.HasXWallReflect(objId) && movement.GetXVelocity(objId) < 0)
                        {
                            movement.SetXVelocity(objId, movement.GetXVelocity(objId)*-1);
                        }
                    }

                    tileId1 = upLeftId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_RIGHT);
                }
                else if (!tcCorners.centerLeft)
                {
                    float oldLeft = collision.GetLeft(objId);
                    float oldX = position.GetX(objId);
                    float xOffset = oldLeft - oldX;
                    float newLeft = centerLeftRight;
                    float newX = newLeft - xOffset;
                    position.SetX(objId, newX);
                    collision.SetLeft(objId, newLeft);
                    collision.SetRight(objId, newLeft+colWidth);

                    if (gravity.Has(objId))
                    {
                        if (gravity.HasXWallReflect(objId) && movement.GetXVelocity(objId) < 0)
                        {
                            movement.SetXVelocity(objId, movement.GetXVelocity(objId)*-1);
                        }
                    }

                    tileId1 = centerLeftId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_RIGHT);
                }
            }
        }
        // Hit from left - works!
        if (xvel > 0)
        {
            if(tcCorners.downRight && tcCorners.upRight && tcCorners.centerRight)
            {
                position.SetX(objId, position.GetX(objId) + xvel);
                collision.SetLeft(objId, collision.GetLeft(objId) + xvel);
                collision.SetRight(objId, collision.GetLeft(objId) + colWidth);
            }
            else
            {
                int downRightId = -1;
                int centerRightId = -1;
                int upRightId = -1;
                int tileId1;
                int tileId2;

                float downRightLeft = 0.f;
                float centerRightLeft = 0.f;
                float upRightLeft = 0.f;
                if (tcCorners.downRight == false)
                {
                    for (unsigned int k = 0; k < tcCorners.downRightIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.downRightIds[k]))
                        {
                            downRightId = tcCorners.downRightIds[k];
                            downRightLeft = collision.GetLeft(tcCorners.downRightIds[k]);
                        }
                    }
                }
                if (tcCorners.upRight == false)
                {
                    for (unsigned int k = 0; k < tcCorners.upRightIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.upRightIds[k]))
                        {
                            upRightId = tcCorners.upRightIds[k];
                            upRightLeft = collision.GetLeft(tcCorners.upRightIds[k]);
                        }
                    }
                }
                if (tcCorners.centerRight == false)
                {
                    for (unsigned int k = 0; k < tcCorners.centerRightIds.size(); k++)
                    {
                        if (collision.IsTile(tcCorners.centerRightIds[k]))
                        {
                            centerRightId = tcCorners.centerRightIds[k];
                            centerRightLeft = collision.GetLeft(tcCorners.centerRightIds[k]);
                        }
                    }
                }
                if (!tcCorners.downRight && !tcCorners.upRight)
                {
                    tileId1 = downRightId;
                    tileId2 = upRightId;
                    if (downRightLeft > upRightLeft)
                    {
                        float oldLeft = collision.GetLeft(objId);
                        float oldX = position.GetX(objId);
                        float xOffset = oldLeft - oldX;
                        float newLeft = downRightLeft-colWidth;
                        float newX = newLeft - xOffset;
                        position.SetX(objId, newX);
                        collision.SetLeft(objId, newLeft);
                        collision.SetRight(objId, newLeft+colWidth);
                    } else {
                        float oldLeft = collision.GetLeft(objId);
                        float oldX = position.GetX(objId);
                        float xOffset = oldLeft - oldX;
                        float newLeft = upRightLeft-colWidth;
                        float newX = newLeft - xOffset;
                        position.SetX(objId, newX);
                        collision.SetLeft(objId, newLeft);
                        collision.SetRight(objId, newLeft+colWidth);
                    }

                    if (gravity.Has(objId))
                    {
                        if (gravity.HasXWallReflect(objId) && movement.GetXVelocity(objId) > 0)
                        {
                            movement.SetXVelocity(objId, movement.GetXVelocity(objId)*-1);
                        }
                    }

                    if (tileId1 == tileId2)
                    {
                        (basegame->*game_tile_collision)(objId, tileId1, COLLISION_LEFT);
                    }
                    else
                    {
                        (basegame->*game_tile_collision)(objId, tileId1, COLLISION_LEFT);
                        (basegame->*game_tile_collision)(objId, tileId2, COLLISION_LEFT);
                    }
                }
                else if (!tcCorners.downRight)
                {
                    float oldLeft = collision.GetLeft(objId);
                    float oldX = position.GetX(objId);
                    float xOffset = oldLeft - oldX;
                    float newLeft = downRightLeft-colWidth;
                    float newX = newLeft - xOffset;
                    position.SetX(objId, newX);
                    collision.SetLeft(objId, newLeft);
                    collision.SetRight(objId, newLeft+colWidth);

                    if (gravity.Has(objId))
                    {
                        if (gravity.HasXWallReflect(objId) && movement.GetXVelocity(objId) > 0)
                        {
                            movement.SetXVelocity(objId, movement.GetXVelocity(objId)*-1);
                        }
                    }

                    tileId1 = downRightId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_LEFT);
                }
                else if (!tcCorners.upRight)
                {
                    float oldLeft = collision.GetLeft(objId);
                    float oldX = position.GetX(objId);
                    float xOffset = oldLeft - oldX;
                    float newLeft = upRightLeft-colWidth;
                    float newX = newLeft - xOffset;
                    position.SetX(objId, newX);
                    collision.SetLeft(objId, newLeft);
                    collision.SetRight(objId, newLeft+colWidth);

                    if (gravity.Has(objId))
                    {
                        if (gravity.HasXWallReflect(objId) && movement.GetXVelocity(objId) > 0)
                        {
                            movement.SetXVelocity(objId, movement.GetXVelocity(objId)*-1);
                        }
                    }

                    tileId1 = upRightId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_LEFT);
                }
                else if (!tcCorners.centerRight)
                {
                    float oldLeft = collision.GetLeft(objId);
                    float oldX = position.GetX(objId);
                    float xOffset = oldLeft - oldX;
                    float newLeft = centerRightLeft-colWidth;
                    float newX = newLeft - xOffset;
                    position.SetX(objId, newX);
                    collision.SetLeft(objId, newLeft);
                    collision.SetRight(objId, newLeft+colWidth);

                    if (gravity.Has(objId))
                    {
                        if (gravity.HasXWallReflect(objId) && movement.GetXVelocity(objId) > 0)
                        {
                            movement.SetXVelocity(objId, movement.GetXVelocity(objId)*-1);
                        }
                    }

                    tileId1 = centerRightId;
                    (basegame->*game_tile_collision)(objId, tileId1, COLLISION_LEFT);
                }
            }
        }
    }
}

void
Engine::GOBCleanup( void (BaseGame::*game_gob_removed)(int id),
                    BaseGame* basegame)
{
    for (int x = 0; x < gameObjects.GetCount(); x++)
    {
        int objId = gameObjects.GetIdByIndex(x);

        if (gameObjects.IsExpired(objId))
        {
            gameObjects.SetAlive(objId, false);
        }

        if (gameObjects.IsAlive(objId) == false)
        {
            gameObjects.RemoveObject(objId);
            if (animation.Has(objId))
            {
                animation.RemoveObject(objId);
            }
            if (collision.Has(objId))
            {
                collision.RemoveObject(objId);
            }
            if (gravity.Has(objId))
            {
                gravity.RemoveObject(objId);
            }
            if (movement.Has(objId))
            {
                movement.RemoveObject(objId);
            }
            if (position.Has(objId))
            {
                position.RemoveObject(objId);
            }
            if (render.Has(objId))
            {
                render.RemoveObject(objId);
            }

            (basegame->*game_gob_removed)(objId);
        }
    }
}

std::vector<int> // Returns a vector of object ids
Engine::CollidesWithPoint(float x, float y)
{
    std::vector<int> objectIds;
    objectIds.clear();
    for (int i = 0; i < collision.GetCount(); i++)
    {
        int objId = collision.GetIdByIndex(i);

        if (gameObjects.IsAlive(objId))
        {
            if (collision.GetRectangle(objId).Contains(x, y))
            {
                objectIds.push_back(objId);
            }
        }
    }

    return objectIds;
}

void
Engine::Close(	void (BaseGame::*game_end)(void),
				BaseGame* basegame)
{
	(basegame->*game_end)();
	screen.Close();
}

} //namespace
